package org.zoeframework.core.inject;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.zoeframework.core.BeanContainer;
import org.zoeframework.core.inject.annotation.Autowire;
import org.zoeframework.util.ClassUtil;

import java.lang.reflect.Field;
import java.util.Set;

/**
 * @author Zoe.Zhang
 * @date 2022/01/19
 * @description
 */
@Slf4j
public class DependencyInjector {
    private BeanContainer beanContainer;

    /**
     * 实例化的时候就加载beanContainer
     */
    public DependencyInjector() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 执行依赖注入
     */
    public void doIoc() {
        // 1：遍历Bean容器中所有的Class对象
        Set<Class<?>> clazzSet = beanContainer.getClasses();
        if (CollUtil.isEmpty(clazzSet)) {
            log.error("clazzSet is empty===>{}", clazzSet);
            return;
        }
        for (Class<?> clazzKey : clazzSet) {
            // 2：遍历Class对象中的所有成员变量
            Field[] declaredFields = clazzKey.getDeclaredFields();
            if (ArrayUtil.isEmpty(declaredFields)) {
                continue;
            }
            for (Field declaredField : declaredFields) {
                // 3：找出被Autowire标记的成员变量
                if (AnnotationUtil.hasAnnotation(declaredField, Autowire.class)) {
                    Autowire annotation = AnnotationUtil.getAnnotation(declaredField, Autowire.class);
                    String autowireValue = annotation.value();
                    // 4：获取这些变量的Clazz类型
                    Class<?> autowireFieldClazz = declaredField.getType();
                    // 5：获取这些成员变量的类型在容器里对应的实例;
                    // 注：通常情况下,成员变量都是接口,但是接口一般都不会被容器注解所修饰,所以我们要通过接口的class对象拿到对应的实现类
                    Object autowireFieldBean = getFieldInstance(autowireFieldClazz, autowireValue);
                    if (ObjectUtil.isNull(autowireFieldBean)) {
                        throw new RuntimeException("unable to inject relevant type , target fileClazz is:" + autowireFieldClazz.getName() + " autowireValue is:" + autowireValue);
                    } else {
                        // 6：通过反射将对应的成员变量实例注入到成员变量所在类的实例里面
                        // 6.1：先获取当前类的实例
                        Object targetBean = beanContainer.getBean(clazzKey);
                        // 6.2：再给当前类的成员变量注入值
                        ClassUtil.setField(declaredField, targetBean, autowireFieldBean, true);
                    }
                }
            }
        }
    }

    /**
     * 根据fieldClazz对象获取容器中对应的实例或者实现类
     *
     * @param fieldClazz
     * @return
     */
    private Object getFieldInstance(Class<?> fieldClazz, String autowireValue) {
        Object fileBean = beanContainer.getBean(fieldClazz);
        // 如果拿到的对象不是空,说明fieldClazz实现类的class对象,直接通过fieldClazz去容器中获取对应的实例对象即可
        if (ObjectUtil.isNotNull(fileBean)) {
            return fileBean;
        }
        // 如果为空的话,说明fieldClazz是接口的class对象,需要fieldClazz拿到对应的实现类
        Class<?> implementsClass = getImplementsClass(fieldClazz, autowireValue);
        if (ObjectUtil.isNotNull(implementsClass)) {
            return beanContainer.getBean(implementsClass);
        } else {
            return null;
        }
    }

    /**
     * 获取接口对应的实现类Clazz对象
     *
     * @param fieldClazz
     * @return
     */
    private Class<?> getImplementsClass(Class<?> fieldClazz, String autowireValue) {
        Set<Class<?>> classesBySuper = beanContainer.getClassesBySuper(fieldClazz);
        if (CollUtil.isNotEmpty(classesBySuper)) {
            if (StrUtil.isBlank(autowireValue)) {
                if (classesBySuper.size() == 1) {
                    return CollUtil.getFirst(classesBySuper.iterator());
                } else {
                    throw new RuntimeException("当前接口有多个实现类:" + fieldClazz.getName() + "  并且没有指定实现类名称");
                }
            } else {
                for (Class<?> clazz : classesBySuper) {
                    if (StrUtil.equals(clazz.getSimpleName(), autowireValue)) {
                        return clazz;
                    }
                }
            }
        }
        return null;
    }
}
